package inabit.inference;

import inabit.base.RuleBase;
import inabit.base.WorkingMemory;
import inabit.data.FireableRule;
import inabit.data.Rule;
import inabit.data.Variable;
import inabit.data.expression.ConsequentExpression;
import inabit.data.expression.Expression;
import inabit.data.expression.LogicExpression;
import inabit.utils.RuleStringFormat;

import java.util.ArrayList;
import java.util.List;

import javax.swing.tree.DefaultTreeModel;
/**Executes backward chaining inference over the working memory
 * @author Rafael Rocha*/
public abstract class BackwardEngine extends InferenceEngine{

	private List<String> goals;
	private DefaultTreeModel trace;
	private String goal;
	private InferenceNode root;
	private List<Expression> askedExpressions;
	
	public BackwardEngine() {
		goals = new ArrayList<String>();
		trace = new DefaultTreeModel(null);
		askedExpressions = new ArrayList<Expression>();
	}

	/**Returns a list of FireableRules that can modify the goal variable passed by parameter*/
	protected List<FireableRule> goalRules(RuleBase rb, WorkingMemory wm, Variable v) {
		List<FireableRule> goalRules = new ArrayList<FireableRule>();
		for (FireableRule rule : rb.getAllFireableRules()) {
			if (rule.getTargetVariables(wm).contains(v)) {
				goalRules.add(rule);
			}
		}
		return goalRules;
	}
	/**Return a specific FireableRule from the conflictSet*/
	protected abstract FireableRule resolveSet(List<FireableRule> conflictSet);
	/**Ask if a expression is true, passing the rule which owns the expression and the current working memory
	 * this method is called by the inference engine when it reaches a leaf at the derivation process*/
	protected abstract Double ask(Expression exp, Rule r, WorkingMemory wm);
	
	/**Returns a name to the root node*/
	protected abstract String getRootNodeLabel(String goal);
	
	/**Returns a name for the inner node at the derivation process*/
	protected abstract String getInnerNodeLabel(Rule rule);
	
	/**Return a name for a goal node at the derivation process*/
	protected abstract String getGoalNodeLabel(Expression exp);

	
	/**Returns a list of variables which value is needed to be known to evaluate the expression*/
	protected List<Variable> goalVariables(WorkingMemory wm, Expression exp){
		List<Variable> goalVariables = new ArrayList<Variable>();
		for (Variable v : exp.getReferencedVariables(wm)) {
			goalVariables.add(v);
		}
		return goalVariables;
	}
	/**Set the variable id to the current goal*/
	public void setGoal(String goalId) {
		this.goal = goalId;
	}
	/**Executes the inference process*/
	public void inference(RuleBase rb, WorkingMemory wm) throws Exception {
		goals.clear();
		askedExpressions.clear();
		rb.resetRules();
		root = new InferenceNode(getRootNodeLabel(goal), false);
		trace.setRoot(root);
		root.setVerified(true);
		backwardChaining(wm, rb, goal, root);
		
	}
	/**Returns a tree representing the derivation process*/
	public DefaultTreeModel getTraceTreeModel() {
		return trace;
	}
	/**Returns the root of derivation process*/
	public InferenceNode getTraceRoot() {
		return root;
	}
	/**Executes recursively the backward chaining process filling the trace Tree*/
	protected void backwardChaining(WorkingMemory wm, RuleBase rb, String goalId, InferenceNode parent) throws Exception {
		parent.content = goalId + "?";
		Variable goal = wm.getVariable(goalId);
		
		if(goals.contains(goalId)){
			return;
		}
		
		goals.add(goalId);
		
		List<FireableRule> goalRules = goalRules(rb, wm, goal);
		List<FireableRule> unknownRules = new ArrayList<FireableRule>();
		
		for(FireableRule rule : goalRules){
			
			Double truth = rule.evaluate(wm);
			
			if(truth == null) {
				unknownRules.add(rule);
			}else {
				if(truth > Double.MIN_VALUE) {
					InferenceNode conclusion = new InferenceNode(getInnerNodeLabel(rule), true);
					conclusion.setVerified(true);
					rule.fire(wm, truth);
				}
				if(goal.getValue() != null) {
					return;
				}
			}
			
		}
		 
		List<FireableRule> secondRound = new ArrayList<FireableRule>();
		
		for(FireableRule rule : unknownRules){
			InferenceNode node = new InferenceNode(getGoalNodeLabel(rule.getConsequent()), false);
			Expression precedent = breakExpression(rule.getPrecedent(), wm);
			
			if(precedent != null) {
				Double truth = rule.evaluate(wm);
				
				if(truth == null) {
					for(Variable var : goalVariables(wm, precedent)){
						backwardChaining(wm, rb, var.getID(), node);
						
						truth = rule.evaluate(wm);
						
						if(truth != null && truth > Double.MIN_VALUE){
							InferenceNode conclusion = new InferenceNode(getInnerNodeLabel(rule), true);
							conclusion.setVerified(true);
							rule.fire(wm, truth);
						}
						if(goal.getValue() != null) {
							return;
						}
						
					}
					
					truth = rule.evaluate(wm);
				}
				
				if(truth == null) {
					secondRound.add(rule);
				}else {
					
					if(truth > Double.MIN_VALUE) {
						if(precedent instanceof ConsequentExpression) {
							((ConsequentExpression)precedent).fire(wm, truth);
						}
						InferenceNode conclusion = new InferenceNode(getInnerNodeLabel(rule), true);
						conclusion.setVerified(true);
						rule.fire(wm, truth);
					}else if(precedent instanceof ConsequentExpression){
						((ConsequentExpression)precedent.negation()).fire(wm, 1-truth);
					}
					if(goal.getValue() != null) {
						return;
					}
				}
			}
		}
		
		for(FireableRule rule : secondRound) {
			InferenceNode node = new InferenceNode(getInnerNodeLabel(rule), false);
			trace.insertNodeInto(node, parent, parent.getChildCount());
			Expression precedent = breakExpression(rule.getPrecedent(), wm);
			
			if(precedent != null) {
				Double truth = rule.evaluate(wm);
				if(truth == null) {
					if(!askedExpressions.contains(precedent)) {
						truth = ask(precedent,rule, wm);
						askedExpressions.add(precedent);
					}
				}
				
				if(truth != null) {
					
					if(truth > Double.MIN_VALUE) {
						if(precedent instanceof ConsequentExpression) {
							((ConsequentExpression)precedent).fire(wm, truth);
						}
						InferenceNode conclusion = new InferenceNode(getInnerNodeLabel(rule), true);
						trace.insertNodeInto(conclusion, node, node.getChildCount());
						conclusion.setVerified(true);
						rule.fire(wm, truth);
					}else if(precedent instanceof ConsequentExpression){
						((ConsequentExpression)precedent.negation()).fire(wm, 1-truth);
					}
					if(goal.getValue() != null) {
						return;
					}
				}
			}
		}
		
		goals.remove(goalId);
		
	}
	/**Returns the unknown part of the expression that doesn't depend of the goal variables
	 * (prepare the expression to be asked to the user)*/
	protected Expression breakExpression(Expression e, WorkingMemory wm) {
		if(e instanceof LogicExpression) {
			LogicExpression log = (LogicExpression)e;
			Expression left = log.getLeftSide();
			Expression right = log.getRightSide();
			boolean leftB = false, rightB = false;
			for(String s : goals) {
				if(left.getReferencedVariablesIds().contains(s)){
					leftB = true;
				}
				if(right.getReferencedVariablesIds().contains(s)) {
					rightB = true;
				}
			}
			if(left.evaluate(wm) != null && !leftB) {
				return breakExpression(right, wm);
			}else if(left.evaluate(wm) != null && !rightB) {
				return breakExpression(left, wm);
			}else if(leftB || rightB){
				return null;
			}else {
				return log;
			}
		}else{
			for(String s : goals) {
				if(e.getReferencedVariablesIds().contains(s)){
					return null;
				}
			}
			return e;
		}
	}

}
